package com.an.blog.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import com.an.blog.admin.domain.BlogArticle;
import com.an.blog.admin.domain.BlogArticleTag;
import com.an.blog.admin.domain.bo.BlogArticleBo;
import com.an.blog.admin.domain.bo.BlogArticleTagBo;
import com.an.blog.admin.domain.vo.BlogArticleTagVo;
import com.an.blog.admin.domain.vo.BlogArticleVo;
import com.an.blog.admin.domain.vo.BlogCategoryVo;
import com.an.blog.mapper.BlogArticleMapper;
import com.an.blog.service.IBlogArticleService;
import com.an.blog.service.IBlogArticleTagService;
import com.an.blog.service.IBlogCategoryService;
import com.an.blog.strategy.context.SearchStrategyContext;
import com.an.blog.third.es.domain.EsBlogArticle;
import com.an.blog.third.es.esmapper.EsBlogArticleMapper;
import com.an.blog.utils.BlogUniversalUtil;
import com.an.blog.utils.ConvertUtil;
import com.an.blog.web.domain.bo.BlogArticleWebBo;
import com.an.blog.web.domain.vo.ArchiveGroupYearWebVo;
import com.an.blog.web.domain.vo.BlogArticleArchiveWebVo;
import com.an.blog.web.domain.vo.BlogArticleDetailWebVo;
import com.an.blog.web.domain.vo.BlogArticleWebVo;
import com.an.common.constant.BlogConstants;
import com.an.common.constant.CacheConstants;
import com.an.common.core.domain.BaseEntity;
import com.an.common.core.domain.PageQuery;
import com.an.common.core.page.TableDataInfo;
import com.an.common.exception.blog.BlogException;
import com.an.common.helper.LoginHelper;
import com.an.common.utils.StreamUtils;
import com.an.common.utils.StringUtils;
import com.an.common.utils.redis.RedisUtils;
import com.an.system.domain.vo.SysOssVo;
import com.an.system.service.ISysConfigService;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.easyes.core.toolkit.EntityInfoHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 博客文章Service业务层处理
 *
 * @author Ann
 * @date 2023-05-08
 */
@RequiredArgsConstructor
@Service
public class BlogArticleServiceImpl implements IBlogArticleService {

    private final EsBlogArticleMapper esArticleMapper;
    private final BlogArticleMapper baseMapper;
    private final IBlogArticleTagService articleTagService;
    private final ISysConfigService sysConfigService;
    private final IBlogCategoryService categoryService;

    private final SearchStrategyContext searchStrategyContext;


    /**
     * 查询博客文章列表
     *
     * @param bo        业务对象
     * @param pageQuery 页面查询
     * @return {@link TableDataInfo }<{@link BlogArticleVo }>
     * @author: Ann
     * @createDate: 2023/05/09 13:51:08
     * @version: 1.0.0
     */
    @Override
    public TableDataInfo<BlogArticleVo> queryPageList(BlogArticleBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<BlogArticle> lqw = buildQueryWrapper(bo);
        Page<BlogArticleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        List<BlogArticleVo> records = result.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            // 查分类，查标签，列表展示
            Set<Long> categoryIds = StreamUtils.toSet(records, BlogArticleVo::getCategoryId);
            List<BlogCategoryVo> categories = categoryService.queryListByIds(categoryIds);
            Map<Long, String> categoryMap = categories.stream()
                .collect(Collectors.toMap(BlogCategoryVo::getCategoryId, BlogCategoryVo::getCategoryName));
            //查询并转换所有涉及到的 oss 记录
            Map<Long, SysOssVo> sysOssVoMap = ConvertUtil
                .convert2OssIdSysOssMap(records, BlogArticleVo::getArticleImage);
            //转换图片 url 和设置分类信息
            records.forEach(record -> {
                SysOssVo sysOssVo = sysOssVoMap.get(record.getArticleImage());
                record.setArticleImageUrl(sysOssVo != null ? sysOssVo.getUrl() : null);
                record.setCategoryName(categoryMap.get(record.getCategoryId()));
            });
        }

        return TableDataInfo.build(result);
    }

    /**
     * 建立查询包装
     *
     * @param bo 业务对象
     * @return {@link LambdaQueryWrapper }<{@link BlogArticle }>
     * @author: Ann
     * @createDate: 2023/05/09 13:51:04
     * @version: 1.0.0
     */
    private LambdaQueryWrapper<BlogArticle> buildQueryWrapper(BlogArticleBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<BlogArticle> lqw = Wrappers.lambdaQuery();
        lqw.select(BlogArticle::getArticleId, BlogArticle::getArticleTitle, BlogArticle::getCategoryId,
            BlogArticle::getArticleImage, BlogArticle::getArticleType, BlogArticle::getIsTop, BlogArticle::getStatus,
            BlogArticle::getLikedCount, BlogArticle::getViewedCount, BaseEntity::getCreateTime);
        lqw.like(StringUtils.isNotBlank(bo.getArticleTitle()), BlogArticle::getArticleTitle, bo.getArticleTitle());
        lqw.eq(StringUtils.isNotBlank(bo.getArticleType()), BlogArticle::getArticleType, bo.getArticleType());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), BlogArticle::getStatus, bo.getStatus());
        lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
            BaseEntity::getCreateTime, params.get("beginTime"), params.get("endTime"));
        lqw.orderByDesc(BaseEntity::getCreateTime);
        return lqw;
    }

    /**
     * 新增博客文章
     *
     * @param bo 业务对象
     * @return {@link Boolean }
     * @author: Ann
     * @createDate: 2023/05/09 14:03:42
     * @version: 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(BlogArticleBo bo) {
        BlogArticle article = BeanUtil.toBean(bo, BlogArticle.class);
        article.setArticleTags(bo.getArticleTags());
        validEntityBeforeSave(article);
        boolean inserted = baseMapper.insert(article) > 0;
        insertArticleTagBatch(article);
        //进行es处理
        BlogUniversalUtil.handleEsOperate(article, Collections.emptyList(), BlogConstants.ARTICLE_INSERT);
        return inserted;
    }

    /**
     * 新增文章标签批量
     *
     * @param add 添加
     * @return boolean
     * @author: Ann
     * @createDate: 2023/10/24 09:56:41
     * @version: 1.0.0
     */
    private boolean insertArticleTagBatch(BlogArticle add) {
        List<BlogArticleTag> articleTagList = StreamUtils.toList(Arrays.asList(add.getArticleTags()), tagId -> {
            BlogArticleTag blogArticleTag = new BlogArticleTag();
            blogArticleTag.setArticleId(add.getArticleId());
            blogArticleTag.setTagId(tagId);
            return blogArticleTag;
        });
        return articleTagService.insertBatch(articleTagList);
    }


    /**
     * 修改博客文章
     *
     * @param bo 业务对象
     * @return {@link Boolean }
     * @author: Ann
     * @createDate: 2023/05/09 14:03:44
     * @version: 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(BlogArticleBo bo) {
        BlogArticle article = BeanUtil.toBean(bo, BlogArticle.class);
        validEntityBeforeSave(article);
        article.setArticleTags(bo.getArticleTags());
        baseMapper.updateById(article);
        //重置标签
        articleTagService.deleteByArticleId(bo.getArticleId());
        boolean inserted = insertArticleTagBatch(article);
        //进行es处理
        BlogUniversalUtil.handleEsOperate(article, Collections.emptyList(), BlogConstants.ARTICLE_UPDATE);
        return inserted;
    }

    /**
     * 保存前的数据校验
     *
     * @param entity 实体
     * @author: Ann
     * @createDate: 2023/05/09 20:08:44
     * @version: 1.0.0
     */
    private void validEntityBeforeSave(BlogArticle entity) {
        if (!BlogConstants.ARTICLE_ORIGINAL_TYPE.equals(entity.getArticleType())) {
            String originalUrl = entity.getOriginalUrl();
            if (StringUtils.isEmpty(originalUrl)) {
                throw new BlogException("blog.article.originalLink.not.blank");
            }
        } else {
            entity.setOriginalUrl(StringUtils.EMPTY);
        }
    }

    /**
     * 批量删除博客文章
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        // 做一些业务上的校验,判断是否需要校验
        boolean deleted = baseMapper.deleteBatchIds(ids) > 0;
        BlogUniversalUtil.handleEsOperate(new BlogArticle(), ids, BlogConstants.ARTICLE_DELETE);
        return deleted;
    }

    /**
     * 更新文章状态
     *
     * @param bo 业务对象
     * @return int
     * @author: Ann
     * @createDate: 2023/05/09 16:50:21
     * @version: 1.0.0
     */
    @Override
    public int updateArticleStatus(BlogArticleBo bo) {
        BlogArticle blogArticle = BeanUtil.toBean(bo, BlogArticle.class);
        int updated = baseMapper.updateById(blogArticle);
        BlogUniversalUtil.handleEsOperate(blogArticle, Collections.emptyList(), BlogConstants.ARTICLE_UPDATE);
        return updated;
    }

    /**
     * 更新文章是顶级
     *
     * @param bo 业务对象
     * @return int
     * @author: Ann
     * @createDate: 2023/05/09 16:50:19
     * @version: 1.0.0
     */
    @Override
    public int updateArticleIsTop(BlogArticleBo bo) {
        BlogArticle blogArticle = BeanUtil.toBean(bo, BlogArticle.class);
        int updated = baseMapper.updateById(blogArticle);
        BlogUniversalUtil.handleEsOperate(blogArticle, Collections.emptyList(), BlogConstants.ARTICLE_UPDATE);
        return updated;
    }

    /**
     * 查询博客文章
     *
     * @param articleId 文章id
     * @return {@link BlogArticleVo }
     * @author: Ann
     * @createDate: 2023/05/09 16:00:57
     * @version: 1.0.0
     */
    @Override
    public BlogArticleVo queryById(Long articleId) {
        BlogArticleVo blogArticleVo = baseMapper.selectVoById(articleId);
        BlogArticleTagBo blogArticleTagBo = new BlogArticleTagBo();
        blogArticleTagBo.setArticleId(articleId);
        List<BlogArticleTagVo> tagVoList = articleTagService.queryList(blogArticleTagBo);
        Long[] tagIds = tagVoList.stream().map(BlogArticleTagVo::getTagId).toArray(Long[]::new);
        blogArticleVo.setArticleTags(tagIds);
        return blogArticleVo;
    }

    /**
     * 初始化 ES 索引
     *
     * @return {@link Boolean }
     * @author Ann
     * @date 2024/06/05
     */
    @Override
    public Boolean initEsIndex() {
        if (!sysConfigService.selectEsEnabled()) {
            throw new BlogException("es.enable.error");
        }
        //1.先检查是否存在索引,存在就删除
        checkIndexExistAndRemove();
        //2.创建索引
        if (!esArticleMapper.createIndex()) {
            throw new BlogException("es.createIndex.error");
        }
        //2.构建es数据
        List<BlogArticle> blogArticleList = baseMapper.selectArticleList4Es();
        List<EsBlogArticle> esBlogArticles = BlogUniversalUtil.buildArticleWebList4Es(blogArticleList);
        //3.插入es
        Integer count = esArticleMapper.insertBatch(esBlogArticles);
        return count == esBlogArticles.size();
    }

    /**
     * 检查索引是否存在并删除
     *
     * @author Ann
     * @date 2024/06/05
     */
    private void checkIndexExistAndRemove() {
        String indexName = EntityInfoHelper.getEntityInfo(EsBlogArticle.class).getIndexName();
        //索引名称--如 blog_article
        Boolean existIndex = esArticleMapper.existsIndex(indexName);
        if (existIndex) {
            esArticleMapper.deleteIndex(indexName);
        }
    }

    /**
     * 查看文章
     *
     * @param articleId 文章编号
     * @return {@link Boolean }
     * @author Ann
     * @date 2024/06/16
     */
    @Override
    public Boolean viewArticle(Long articleId) {
        //浏览博客时，浏览量加1
        int update = baseMapper.update(null, new LambdaUpdateWrapper<BlogArticle>()
            .setSql("viewed_count = viewed_count + 1")
            .eq(BlogArticle::getArticleId, articleId));
        return update > 0;
    }


    /**
     * 查询页面列表
     *
     * @param articleWebBo 文章网络业务对象
     * @param pageQuery    页面查询
     * @return {@link TableDataInfo }<{@link BlogArticleWebVo }>
     * @author: Ann
     * @createDate: 2023/05/26 16:34:23
     * @version: 1.0.0
     */
    @Override
    public TableDataInfo<BlogArticleWebVo> queryWebPageList(BlogArticleWebBo articleWebBo, PageQuery pageQuery) {
        Boolean esEnabled = sysConfigService.selectEsEnabled();
        return searchStrategyContext.executeQueryWebPageList(esEnabled, articleWebBo, pageQuery);
    }

    /**
     * 查询网站文章id
     *
     * @param articleId 文章id
     * @return {@link BlogArticleDetailWebVo }
     * @author: Ann
     * @createDate: 2023/06/07 18:43:30
     * @version: 1.0.0
     */
    @Override
    public BlogArticleDetailWebVo queryWebArticleById(Long articleId) {
        Boolean esEnabled = sysConfigService.selectEsEnabled();
        return searchStrategyContext.executeQueryWebDetailById(esEnabled, articleId);
    }


    /**
     * 喜欢文章
     *
     * @param articleId 文章id
     * @return boolean
     * @author: Ann
     * @createDate: 2023/06/07 14:00:49
     * @version: 1.0.0
     */
    @Override
    public Boolean likeArticle(Long articleId) {
        //1.获取登录用户
        Long userId = LoginHelper.getUserId();
        String key = CacheConstants.ARTICLE_LIKED_KEY + articleId;
        //2.判断用户是否点过赞
        boolean isMember = RedisUtils.isScoreSortedSetMember(key, userId);
        boolean isSuccess;
        if (!isMember) {
            //3.如果未点赞，可以点赞
            //3.1 数据库点赞数 +1
            isSuccess = baseMapper.update(null, new LambdaUpdateWrapper<BlogArticle>()
                .setSql("liked_count = liked_count + 1")
                .eq(BlogArticle::getArticleId, articleId)) > 0;
            //3.2 保存用户到redis 的set集合
            if (isSuccess) {
                isSuccess = RedisUtils.addScoreSortedSet(key, userId);
            }
        } else {
            //4. 如果已点赞，取消点赞
            //4.1 数据库 点赞数-1
            isSuccess = baseMapper.update(null, new LambdaUpdateWrapper<BlogArticle>()
                .setSql("liked_count = liked_count - 1")
                .eq(BlogArticle::getArticleId, articleId)) > 0;
            //4.2 redis移除
            if (isSuccess) {
                RedisUtils.removeScoreSortedSet(key, userId);
            }
        }
        return isSuccess;
    }

    /**
     * 查询web归档列表
     *
     * @return {@link List }<{@link ArchiveGroupYearWebVo }>
     * @author: Ann
     * @createDate: 2023/11/30 10:52:59
     * @version: 1.0.0
     */
    @Override
    public List<ArchiveGroupYearWebVo> queryWebArchiveList() {
        List<BlogArticle> blogArticleList = baseMapper.selectList(new LambdaQueryWrapper<BlogArticle>()
            .select(BlogArticle::getArticleId, BlogArticle::getArticleTitle, BaseEntity::getCreateTime)
            .eq(BlogArticle::getStatus, BlogConstants.NORMAL)
            .orderByDesc(BaseEntity::getCreateTime));

        Map<Integer, List<BlogArticle>> yearBlogArticleMap = StreamUtils
            .groupByKey(blogArticleList, blogArticle -> DateUtil.year(blogArticle.getCreateTime()));

        return yearBlogArticleMap.entrySet().stream().map(entry -> {
            ArchiveGroupYearWebVo archiveGroupYearWebVo = new ArchiveGroupYearWebVo();
            archiveGroupYearWebVo.setYear(entry.getKey() + "年");
            LinkedHashMap<String, List<BlogArticleArchiveWebVo>> articleArchiveMap = entry.getValue().stream()
                .map(blogArticle -> BeanUtil.toBean(blogArticle, BlogArticleArchiveWebVo.class))
                .collect(Collectors.groupingBy(blogArticleArchiveWebVo ->
                        DateUtil.month(blogArticleArchiveWebVo.getCreateTime()) + 1 + "月",
                    LinkedHashMap::new, Collectors.toList()));
            archiveGroupYearWebVo.setArchiveMap(articleArchiveMap);
            return archiveGroupYearWebVo;
        }).collect(Collectors.toList());
    }

    /**
     * 查询我文章
     *
     * @return {@link BlogArticleDetailWebVo }
     * @author: Ann
     * @createDate: 2023/12/01 14:14:00
     * @version: 1.0.0
     */
    @Override
    public BlogArticleDetailWebVo queryAboutMeArticle() {
        Long articleId = BlogConstants.ABOUT_ME_ARTICLE_ID;
        BlogArticle blogArticle = baseMapper.selectOne(new LambdaQueryWrapper<BlogArticle>().select(BlogArticle::getArticleContent)
            .eq(BlogArticle::getArticleId, articleId));
        String articleContent = blogArticle.getArticleContent();
        BlogArticleDetailWebVo blogArticleDetailWebVo = new BlogArticleDetailWebVo();
        blogArticleDetailWebVo.setArticleContent(articleContent);
        return blogArticleDetailWebVo;
    }

    /**
     * 构建web查询包装
     * 已弃用
     *
     * @param bo 业务对象
     * @return {@link Wrapper }<{@link BlogArticle }>
     * @author: Ann
     * @createDate: 2023/05/26 21:40:47
     * @version: 1.0.0
     */
    @Deprecated
    private Wrapper<BlogArticle> buildWebQueryWrapper(BlogArticleWebBo bo) {
        QueryWrapper<BlogArticle> wrapper = Wrappers.query();
        wrapper.like(StringUtils.isNotBlank(bo.getKeyword()), "ba.article_title", bo.getKeyword()).or()
            .like(StringUtils.isNotBlank(bo.getKeyword()), "ba.article_content", bo.getKeyword());
        wrapper.eq(bo.getCategoryId() != null, "ba.category_id", bo.getCategoryId());
        wrapper.eq("ba.status", BlogConstants.NORMAL);
        wrapper.eq("ba.del_flag", BlogConstants.NORMAL);
        wrapper.in(ArrayUtil.isNotEmpty(bo.getTagIds()), "bat.tag_id", bo.getTagIds());
        wrapper.groupBy("ba.article_id");
        wrapper.orderByDesc("ba.is_top");
        wrapper.orderByDesc("ba.create_time");
        return wrapper;
    }

    public static void main(String[] args) {

    }
}
